﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
#if UNITY_EDITOR
using System.Reflection;
using UnityEditor;
using UnityEditor.Callbacks;
#endif
using UnityEngine;
using Debug = UnityEngine.Debug;
using System.Text.RegularExpressions;

namespace Utility {
///
/// 在UnityConsole窗口，点击Log跳转到代码文件时，排除自定义Log代码文件。
///
/// 将Log接口集中在一个文件里，加入Log文件列表（logFilePaths）。
///
/// 操作步骤
/// 1、点击Log。得到原始的文件ID和行号
/// 2、如果文件ID不属于预置文件，直接打开。
/// 3、如果文件ID属于预置的log文件列表，尝试提取调用堆栈的上一级文件，并打开。如果没有其他的文件，就继续打开原文件。
///
public class LogTrackSkip {

#if UNITY_EDITOR
    private static List<int> s_InstanceIDs = new List<int>();
    //ConsoleWindow
    private static object s_ConsoleWindow;
    private static object s_LogListView;
    private static FieldInfo s_LogListViewTotalRows;
    private static FieldInfo s_LogListViewCurrentRow;
    //LogEntry
    private static MethodInfo s_LogEntriesGetEntry;
    private static object s_LogEntry;
    //instanceId 非UnityEngine.Object的运行时 InstanceID 为零所以只能用 LogEntry.Condition 判断
    private static FieldInfo s_LogEntryInstanceId;
    private static FieldInfo s_LogEntryLine;
    private static FieldInfo s_LogEntryFile;
    private static FieldInfo s_LogEntryCondition;

    private static List<string> logFilePaths = new List<string>() {
        "Assets/Log/ILogExtensions.cs",
        "Assets/Log/LogTrackSkip.cs",
        "Assets/Log/TestLoadConfig.cs"
    };

    //静态构造，类的静态方法（OnOpenAsset）被调用时会调用。
    static LogTrackSkip() {
        s_InstanceIDs.Clear();
        for(int i = 0; i < logFilePaths.Count; i++) {
            var scriptobj = AssetDatabase.LoadAssetAtPath<MonoScript>(logFilePaths[i]);
            if(scriptobj != null) {
                s_InstanceIDs.Add(scriptobj.GetInstanceID());
            }
        }

        GetConsoleWindowListView();
    }

    private static void GetConsoleWindowListView() {
        if(s_LogListView == null) {
            Assembly unityEditorAssembly = Assembly.GetAssembly(typeof(EditorWindow));
            Type consoleWindowType = unityEditorAssembly.GetType("UnityEditor.ConsoleWindow");
            FieldInfo fieldInfo = consoleWindowType.GetField("ms_ConsoleWindow", BindingFlags.Static | BindingFlags.NonPublic);
            s_ConsoleWindow = fieldInfo.GetValue(null);
            FieldInfo listViewFieldInfo = consoleWindowType.GetField("m_ListView", BindingFlags.Instance | BindingFlags.NonPublic);
            s_LogListView = listViewFieldInfo.GetValue(s_ConsoleWindow);
            s_LogListViewTotalRows = listViewFieldInfo.FieldType.GetField("totalRows", BindingFlags.Instance | BindingFlags.Public);
            s_LogListViewCurrentRow = listViewFieldInfo.FieldType.GetField("row", BindingFlags.Instance | BindingFlags.Public);
            //LogEntries和LogEntry
            Type logEntriesType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntries");
            s_LogEntriesGetEntry = logEntriesType.GetMethod("GetEntryInternal", BindingFlags.Static | BindingFlags.Public);
            Type logEntryType = unityEditorAssembly.GetType("UnityEditorInternal.LogEntry");
            s_LogEntry = Activator.CreateInstance(logEntryType);
            s_LogEntryInstanceId = logEntryType.GetField("instanceID", BindingFlags.Instance | BindingFlags.Public);
            s_LogEntryLine = logEntryType.GetField("line", BindingFlags.Instance | BindingFlags.Public);
            s_LogEntryFile = logEntryType.GetField("file", BindingFlags.Instance | BindingFlags.Public);
            s_LogEntryCondition = logEntryType.GetField("condition", BindingFlags.Instance | BindingFlags.Public);
        }
    }

    static bool IsLogOutputString(string log) {
        return log.StartsWith(ILogExtensions.LogPrefix);
    }

    private static StackInfo ExtractNonLogStackInfo(StackInfo stInfo) {
        if(stInfo == null) return null;
        string[] strStacks = stInfo.content.Split('\n');
        string regexFileInfo = "\\(at .*\\)";
        for(int i = strStacks.Length - 1; i >= 0 ; i--) {
            //获取字符串"(at xxxx:11)"
            Match match = Regex.Match(strStacks[i], regexFileInfo);
            string strFileInfo = match.Value;
            if(string.IsNullOrEmpty(strFileInfo)) continue;
            //字符串“xxx:11”
            strFileInfo = strFileInfo.Substring(4, strFileInfo.Length - 1 - 4);
            string[] strInfo = strFileInfo.Split(':');
            if(strInfo.Length != 2) continue;

            if(false == logFilePaths.Contains(strInfo[0])) {
                return new StackInfo(strInfo[0], Convert.ToInt32(strInfo[1]), stInfo.content);
            }
        }
        return null;
    }

    private static StackInfo GetSelectRowInfo() {
        GetConsoleWindowListView();
        if(s_LogListView != null) {
            int row = (int)s_LogListViewCurrentRow.GetValue(s_LogListView);
            s_LogEntriesGetEntry.Invoke(null, new object[] { row, s_LogEntry });
            if(s_LogEntry != null) {
                string selectLog = s_LogEntryCondition.GetValue(s_LogEntry) as string;
                string lineobj = s_LogEntryLine.GetValue(s_LogEntry).ToString();
                int line = Convert.ToInt32(lineobj);
                string file = s_LogEntryFile.GetValue(s_LogEntry) as string;
                StackInfo selStInfo = new StackInfo(file, line, selectLog);
                Debug.LogFormat("你选择的Log是：{0}", selStInfo.DumpStr());
                return selStInfo;
            }
        }
        return null;
    }

    [UnityEditor.Callbacks.OnOpenAssetAttribute(0)]
    public static bool OnOpenAsset(int instanceID, int line) {
        Debug.LogFormat("打开文件{0}:{1}", instanceID, line);
        if(s_InstanceIDs.Contains(instanceID)) {
            StackInfo stInfo = ExtractNonLogStackInfo(GetSelectRowInfo());
            if(stInfo != null) {
                Debug.LogFormat("提取文件信息 {0}", stInfo.DumpStr());
                AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath<MonoScript>(stInfo.filePath), stInfo.line);
                return true;
            }
        }

        return false;
    }

    class StackInfo {
        public int line;
        public string filePath;
        public string content;
        public StackInfo(string aFilePath, int aLine, string aContent) {
            line = aLine;
            filePath = aFilePath;
            content = aContent;
        }

        public string DumpStr() {
            return string.Format("[StackInfo]({0}:{1})\n{2}^^^^^^^^^^^^^^^^^^^\n", filePath, line, content);
        }
    }
#endif
}

}